package com.example.springboottools.zip;


import cn.hutool.core.date.StopWatch;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import static cn.hutool.core.io.IoUtil.close;
import static org.springframework.util.FileCopyUtils.BUFFER_SIZE;

/***
 * 压缩文件处理类
 * @author lyx
 *
 */
public class ZipUtil {
	static final int BUFFER = 8192;

	public static void main(String[] args) throws Exception {
        String inputFile = "D:\\staples\\日常记录\\工作记录\\2024\\12\\新增商品3.0\\全包图文\\历史图文包\\主图包\\上传文件"+ ".zip";
        String outputFile = "D:\\staples\\日常记录\\工作记录\\2024\\12\\新增商品3.0\\全包图文\\历史图文包\\主图包\\上传文件";
        zipUncompress(inputFile, outputFile);

    }

    public static void zipFolder(String sourceFolderPath, String zipFilePath) throws IOException {
        File folderToZip = new File(sourceFolderPath);
        FileOutputStream fos = new FileOutputStream(zipFilePath);
        ZipOutputStream zos = new ZipOutputStream(fos);

        addFolderToZip(folderToZip, folderToZip.getName(), zos);

        zos.close();
        fos.close();
    }

    private static void addFolderToZip(File folder, String parentFolderName, ZipOutputStream zos) throws IOException {
        for (File file : folder.listFiles()) {
            if (file.isDirectory()) {
                addFolderToZip(file, parentFolderName + "/" + file.getName(), zos);
                continue;
            }

            FileInputStream fis = new FileInputStream(file);
            ZipEntry zipEntry = new ZipEntry(parentFolderName + "/" + file.getName());
            zos.putNextEntry(zipEntry);

            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) >= 0) {
                zos.write(bytes, 0, length);
            }

            fis.close();
        }
    }



	/**
	 * 压缩目录
	 * @param zipFileName
	 * @param target
	 * @throws IOException
	 */
    public static void compreSsion(String zipFileName, File target) throws IOException {//压缩
        ZipOutputStream out=new ZipOutputStream(new FileOutputStream(zipFileName));
        BufferedOutputStream bos=new BufferedOutputStream(out);
        zip(out,target,target.getName(),bos);
        bos.close();
        out.close();
        
    }
    /**
     * 压缩文件组
     * @param zipFileName
     * @param filePaths
     * @throws IOException
     */
    public static void compreSessionFiles(String zipFileName,String...filePaths)throws IOException{
        
        ZipOutputStream out=new ZipOutputStream(new FileOutputStream(zipFileName));
        for(String filePath:filePaths){
        	if(filePath==null || filePath.equals("")) continue;
        	File target=new File(filePath);
        	compressFile(target, out);
        }
        out.close();
        
    }
    /**
     * 压缩文件组
     * @param zipFileName
     * @param filePaths
     * @throws IOException
     */
    public static void compreSessionStampFiles(String zipFileName,String...filePaths)throws IOException{

        ZipOutputStream out=new ZipOutputStream(new FileOutputStream(zipFileName));
        for(String filePath:filePaths){
        	if(filePath==null || filePath.equals("")) continue;
        	File target=new File(filePath);
            compressStampFile(target, out);
        }
        out.close();

    }

    /**
     * 压缩文件组
     * @param zipFileName
     * @param filePaths
     * @throws IOException
     */
    public static void compreSessionFilesS3(String s3Url,String zipFileName, String...filePaths)throws IOException {

        ZipOutputStream out=new ZipOutputStream(new FileOutputStream(zipFileName));
        for(String filePath:filePaths){
            if(filePath==null || filePath.equals("")) continue;
            compressFileS3(s3Url,filePath, out);
        }
        out.close();

    }
    /**
     * 把中文转成Unicode码
     *
     * @param str
     * @return
     */
    public static String chinaToUnicode(String str) {
        String result = "";
        for (int i = 0; i < str.length(); i++) {
            int chr1 = (char) str.charAt(i);
            if (chr1 >= 19968 && chr1 <= 171941) {// 汉字范围 \u4e00-\u9fa5 (中文)
                result += "\\u" + Integer.toHexString(chr1);
            } else {
                result += str.charAt(i);
            }
        }
        return result;
    }

    /**
     * 将文件字节集合压缩 by lyx on 2022-05-01
     * @param files 文件字节集合
     * @return
     * @throws Exception
     */
    public static byte[] compressByte(Map<String,byte[]> files) throws Exception{
        if(files == null || files.isEmpty())
            throw new RuntimeException("文件集合为空");

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream zos=new ZipOutputStream(baos);

        for (Map.Entry<String,byte[]> entry : files.entrySet()) {
            zos.putNextEntry(new ZipEntry(entry.getKey())); //文件名称
            zos.write(entry.getValue()); //文件流

        }
        zos.closeEntry();
        zos.close();
        return baos.toByteArray();
    }

    /**
     * 将文件字节集合压缩，并写入本地 by lyx on 2022-05-01
     * @param files 文件字节集合
     * @param path 压缩包存放地址
     * @return
     * @throws Exception
     */
    public static void compressByte(Map<String,byte[]> files, String path) throws Exception{
        byte [] zip = compressByte(files);

        File zipFile = new File(path);
        if(!zipFile.getParentFile().exists()){
            zipFile.getParentFile().mkdirs();
        }

        FileOutputStream fos = new FileOutputStream(zipFile);
        BufferedOutputStream bos = new BufferedOutputStream(fos); //增加缓存区，提高写入效率
        bos.write(zip);
        bos.close();
        fos.close();
    }


    private static void compressFileS3(String s3Url,String pathUrl, ZipOutputStream out) {

        try {
       /*     BufferedInputStream bis = new BufferedInputStream(
                    new FileInputStream(file));*/
            //String pathUrlUnicode=chinaToUnicode(pathUrl);
            String getPathUrl = utfURL(s3Url+pathUrl);
            URL url = new URL(getPathUrl);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            InputStream in = urlConnection.getInputStream();
            ZipEntry entry = new ZipEntry(pathUrl);
            out.putNextEntry(entry);
            int count;
            byte data[] = new byte[BUFFER];
            while ((count = in.read(data, 0, BUFFER)) != -1) {
                out.write(data, 0, count);
            }
            in.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    static String utfURL(String str) throws UnsupportedEncodingException {
        StringBuffer sb = new StringBuffer();
        int len = str.length();
        for (int i = 0; i < len; i++) {
            char c = str.charAt(i);
            if (c > 0x7F) {
                sb.append(URLEncoder.encode(String.valueOf(c), "utf-8"));
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }
 
    private static void zip(ZipOutputStream zout, File target, String name, BufferedOutputStream bos) throws IOException {
    	if (!target.exists()) {
            return;
        }
    	
    	//判断是不是目录
        if (target.isDirectory()){
          File[] files=target.listFiles();
          if (files.length==0){//空目录
              zout.putNextEntry(new ZipEntry(name+"/"));
            /*  开始编写新的ZIP文件条目，并将流定位到条目数据的开头。
              关闭当前条目，如果仍然有效。 如果没有为条目指定压缩方法，
              将使用默认压缩方法，如果条目没有设置修改时间，将使用当前时间。*/
          }
          for (File f:files){
              //递归处理
              zip(zout,f,name+"/"+f.getName(),bos);
          }
        }else {
                zout.putNextEntry(new ZipEntry(name));
                InputStream inputStream=new FileInputStream(target);
                BufferedInputStream bis=new BufferedInputStream(inputStream);
                byte[] bytes=new byte[2048];
                int len=-1;
                while ((len=bis.read(bytes))!=-1){
                    bos.write(bytes,0,len);
                }
                bis.close();
                inputStream.close();
 
        }
 
    }

    /**
     * 压缩成ZIP 方法1
     *
     * @param srcDir           压缩文件夹路径
     * @param out              压缩文件输出流
     * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
     *                         false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     * @throws RuntimeException 压缩失败会抛出运行时异常
     */
    public static void toZip(String srcDir, OutputStream out, boolean KeepDirStructure)
            throws RuntimeException {
        long start = System.currentTimeMillis();
        ZipOutputStream zos = null;
        try {
            zos = new ZipOutputStream(out);
            File sourceFile = new File(srcDir);
            compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure);
            long end = System.currentTimeMillis();
            System.out.println("压缩完成，耗时：" + (end - start) + " ms");
        } catch (Exception e) {
            throw new RuntimeException("zip error from ZipUtils", e);
        } finally {
            if (zos != null) {
                try {
                    zos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * 压缩成ZIP 方法2
     *
     * @param urls 需要压缩的文件列表
     * @param out      压缩文件输出流
     * @throws RuntimeException 压缩失败会抛出运行时异常
     */
    public static void toBatchFileZip(List<String> urls, OutputStream out) throws RuntimeException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("toZip-2");
        ZipOutputStream zos = null;
        try {
             zos = new ZipOutputStream(out);
            byte[] buffer = new byte[4096];
            for (String urlString : urls) {
                URL url = new URL(urlString);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("HEAD");
                int statusCode = connection.getResponseCode();
                if (statusCode != HttpURLConnection.HTTP_OK){
                    continue;
                }
                InputStream is = url.openStream();
                String entryName = urlString.substring(urlString.lastIndexOf("/") + 1);
                zos.putNextEntry(new ZipEntry(entryName));

                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    zos.write(buffer, 0, bytesRead);
                }

                zos.closeEntry();
                is.close();
            }

            zos.close();
        } catch (Exception e) {
            System.out.println("压缩异常："+ JSON.toJSONString(e));
            throw new RuntimeException("压缩zip出错");
        } finally {
            close(zos);
        }
        stopWatch.stop();
    }
    /**
     * 压缩成ZIP 方法2
     *
     * @param srcFiles 需要压缩的文件列表
     * @param out      压缩文件输出流
     * @throws RuntimeException 压缩失败会抛出运行时异常
     */
    public static void toZip(List<File> srcFiles, OutputStream out) throws RuntimeException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("toZip-2");
        ZipOutputStream zos = null;
        try {
            zos = new ZipOutputStream(out);
            for (File sourceFile : srcFiles) {
                byte[] buf = new byte[BUFFER_SIZE];
                zos.putNextEntry(new ZipEntry(sourceFile.getName()));
                int len;
                FileInputStream in = new FileInputStream(sourceFile);
                while ((len = in.read(buf)) != -1) {
                    zos.write(buf, 0, len);
                }
                zos.closeEntry();
                in.close();
            }
        } catch (Exception e) {
            throw new RuntimeException("压缩zip出错");
        } finally {
            close(zos);
        }
        stopWatch.stop();
    }
    /**
     * 压缩成ZIP
     *
     * @param baisByte 需要压缩的文件列表
     * @param out      压缩文件输出流
     * @throws RuntimeException 压缩失败会抛出运行时异常
     */
    public static void toZip(byte[] baisByte, OutputStream out) throws RuntimeException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("toZip-2");
        ZipOutputStream zos = null;
        try {
            zos = new ZipOutputStream(out);
            byte[] buf = new byte[BUFFER_SIZE];
            zos.putNextEntry(new ZipEntry(System.currentTimeMillis()+".xls"));
            int len;
            ByteArrayInputStream in = new ByteArrayInputStream(baisByte);
            while ((len = in.read(buf)) != -1) {
                zos.write(buf, 0, len);
            }
            zos.closeEntry();
            in.close();
        } catch (Exception e) {
            throw new RuntimeException("压缩zip出错");
        } finally {
            close(zos);
        }
        stopWatch.stop();
    }
    /**
     * 压缩成ZIP
     *
     * @param baisByte 需要压缩的文件列表
     * @param out      压缩文件输出流
     * @throws RuntimeException 压缩失败会抛出运行时异常
     */
    public static void toByteListZip(byte[] baisByte, File file,OutputStream out) throws RuntimeException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("toZip-2");
        ZipOutputStream zos = null;
        try {
            zos = new ZipOutputStream(out);
                byte[] buf = new byte[BUFFER_SIZE];
                zos.putNextEntry(new ZipEntry(System.currentTimeMillis()+".xls"));
                int len;
                ByteArrayInputStream in = new ByteArrayInputStream(baisByte);
                while ((len = in.read(buf)) != -1) {
                    zos.write(buf, 0, len);
                }
                zos.closeEntry();
                in.close();
            zos.putNextEntry(new ZipEntry(file.getName()));
            int len2;
            FileInputStream in2 = new FileInputStream(file);
            while ((len2 = in2.read(buf)) != -1) {
                zos.write(buf, 0, len2);
            }
            zos.closeEntry();
            in2.close();
        } catch (Exception e) {
            throw new RuntimeException("压缩zip出错");
        } finally {
            close(zos);
        }
        stopWatch.stop();
    }
    /**
     * 递归压缩方法
     *
     * @param sourceFile       源文件
     * @param zos              zip输出流
     * @param name             压缩后的名称
     * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
     *                         false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     * @throws Exception
     */
    private static void compress(File sourceFile, ZipOutputStream zos, String name,
                                 boolean KeepDirStructure) throws Exception {
        byte[] buf = new byte[2 * 1024];
        if (sourceFile.isFile()) {
            // 向zip输出流中添加一个zip实体，构造器中name为zip实体的文件的名字
            zos.putNextEntry(new ZipEntry(name));
            // copy文件到zip输出流中
            int len;
            FileInputStream in = new FileInputStream(sourceFile);
            while ((len = in.read(buf)) != -1) {
                zos.write(buf, 0, len);
            }
            // Complete the entry
            zos.closeEntry();
            in.close();
        } else {
            File[] listFiles = sourceFile.listFiles();
            if (listFiles == null || listFiles.length == 0) {
                // 需要保留原来的文件结构时,需要对空文件夹进行处理
                if (KeepDirStructure) {
                    // 空文件夹的处理
                    zos.putNextEntry(new ZipEntry(name + "/"));
                    // 没有文件，不需要文件的copy
                    zos.closeEntry();
                }
            } else {
                for (File file : listFiles) {
                    // 判断是否需要保留原来的文件结构
                    if (KeepDirStructure) {
                        // 注意：file.getName()前面需要带上父文件夹的名字加一斜杠,
                        // 不然最后压缩包中就不能保留原来的文件结构,即：所有文件都跑到压缩包根目录下了
                        compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
                    } else {
                        compress(file, zos, file.getName(), KeepDirStructure);
                    }
                }
            }
        }
    }

    private static void compressFile(File file, ZipOutputStream out) {
        if (!file.exists()) {
            return;
        }
        try {
            BufferedInputStream bis = new BufferedInputStream(
                    new FileInputStream(file));
            ZipEntry entry = new ZipEntry(file.getName());
            out.putNextEntry(entry);
            int count;
            byte data[] = new byte[BUFFER];
            while ((count = bis.read(data, 0, BUFFER)) != -1) {
                out.write(data, 0, count);
            }
            bis.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    private static void compressStampFile(File file, ZipOutputStream out) {
        if (!file.exists()) {
            return;
        }
        try {
            BufferedInputStream bis = new BufferedInputStream(
                    new FileInputStream(file));
            String name = file.getName();
            String[] nameArray = name.split("_");
            if(nameArray.length > 1) {
                name = nameArray[0]+nameArray[1].substring(nameArray[1].lastIndexOf("."));
            }
            ZipEntry entry = new ZipEntry(name);
            out.putNextEntry(entry);
            int count;
            byte data[] = new byte[BUFFER];
            while ((count = bis.read(data, 0, BUFFER)) != -1) {
                out.write(data, 0, count);
            }
            bis.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * zip文件解压
     * @param inputFile  待解压文件夹/文件
     * @param destDirPath  解压路径
     */
    public static void zipUncompress(String inputFile,String destDirPath) throws Exception {

        File destFile = new File(destDirPath);
        if(destFile.exists()) deleteDir(destFile);

        File srcFile = new File(inputFile);//获取当前压缩文件
        // 判断源文件是否存在
        if (!srcFile.exists()) {
            throw new Exception(srcFile.getPath() + "所指文件不存在");
        }
        ZipFile zipFile = null ;
        InputStream is = null ;
        FileOutputStream fos = null ;
        try {
            zipFile = new ZipFile(srcFile, Charset.forName("GBK"));//创建压缩文件对象
            //开始解压
            Enumeration<?> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                // 如果是文件夹，就创建个文件夹
                if (entry.isDirectory()) {
                    /*String dirPath = destDirPath + "/" + entry.getName();
                    //先删除再创建
                    File filePath = new File(dirPath);
                    if(filePath.exists()){
                        deleteDir(filePath) ;
                    }*/
                    srcFile.mkdirs();
                } else {
                    // 如果是文件，就先创建一个文件，然后用io流把内容copy过去
                    File targetFile = new File(destDirPath + "/" + entry.getName());
                    // 保证这个文件的父文件夹必须要存在
                    if (!targetFile.getParentFile().exists()) {
                        targetFile.getParentFile().mkdirs();
                    }
                    targetFile.createNewFile();
                    // 将压缩文件内容写入到这个文件中
                    is = zipFile.getInputStream(entry);
                    fos = new FileOutputStream(targetFile);
                    int len;
                    byte[] buf = new byte[1024];
                    while ((len = is.read(buf)) != -1) {
                        fos.write(buf, 0, len);
                    }
                    // 关流顺序，先打开的后关闭
                    fos.close();
                    is.close();
                }
            }
            zipFile.close();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(zipFile!=null){
                zipFile.close();
            }
            if(fos!=null){
                fos.close();
            }
            if(is!=null){
                is.close();
            }
        }

    }

    /**
     * zip文件解压
     *
     * @param inputFile   待解压文件夹/文件
     * @param destDirPath 解压路径
     * @param firstDir 一级目录重命名
     */
    public static void zipUncompress(String inputFile, String destDirPath, List<String> filePathName, String firstDir) throws Exception {
        File srcFile = new File(inputFile);//获取当前压缩文件
        // 判断源文件是否存在
        if (!srcFile.exists()) {
            throw new Exception(srcFile.getPath() + "所指文件不存在");
        }
        ZipFile zipFile = null;
        InputStream is = null;
        FileOutputStream fos = null;
        String oldFirstDir = "";
        try {
            zipFile = new ZipFile(srcFile, Charset.forName("GBK"));//创建压缩文件对象
            //开始解压
            Enumeration<?> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                // 如果是文件夹，就创建个文件夹
                if (entry.isDirectory()) {
                    String dirPath = destDirPath + "/" + entry.getName();
                    filePathName.add(entry.getName());

                    // 一级目录重命名
                    if(StringUtils.isNotEmpty(firstDir)){
                        if(StringUtils.isEmpty(oldFirstDir)){
                            dirPath = destDirPath + "/" + firstDir;
                            oldFirstDir = entry.getName().replaceAll("/","");
                        }else{
                            dirPath = dirPath.replace(oldFirstDir,firstDir);
                        }
                    }

                    //先删除再创建
                    File filePath = new File(dirPath);
                    if (filePath.exists()) {
                        deleteDir(filePath);
                    }
                    filePath.mkdirs();
                } else {
                    String path = destDirPath + "/" + entry.getName();
                    // 一级目录重命名
                    if(StringUtils.isNotEmpty(firstDir) && StringUtils.isNotEmpty(oldFirstDir)){
                        path = path.replace(oldFirstDir,firstDir);
                    }

                    // 如果是文件，就先创建一个文件，然后用io流把内容copy过去
                    File targetFile = new File(path);
                    // 保证这个文件的父文件夹必须要存在
                    if (!targetFile.getParentFile().exists()) {
                        targetFile.getParentFile().mkdirs();
                    }
                    targetFile.createNewFile();
                    // 将压缩文件内容写入到这个文件中
                    is = zipFile.getInputStream(entry);
                    fos = new FileOutputStream(targetFile);
                    int len;
                    byte[] buf = new byte[1024];
                    while ((len = is.read(buf)) != -1) {
                        fos.write(buf, 0, len);
                    }
                    // 关流顺序，先打开的后关闭
                    fos.close();
                    is.close();
                }
            }
            zipFile.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (zipFile != null) {
                zipFile.close();
            }
            if (fos != null) {
                fos.close();
            }
            if (is != null) {
                is.close();
            }
        }

    }

    private static boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            //递归删除目录中的子目录下
            for (int i=0; i<children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        // 目录此时为空，可以删除
        return dir.delete();
    }
}

